﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LED_Editor.Model;
using System.Diagnostics;

namespace LED_Editor.ControllerProgramming {
	public class ExtendedControllerProgrammingStrategy : ControllerProgrammingStrategyBase {
		#region IControllerProgrammingStrategy Members


		public void ReportError(string errorText) {
			throw new ControllerException(string.Format("Состояние: {0}, Адрес: {1}. {2}", State, Address, errorText));
		}

		public override bool IsReadyForProgramming {
			get {
				try {
					Query echo = Query.CreateInstance(QueryCodes.ECHO, Address);
					SendQuery(echo);
					// Query answer = (Query)WaitAnswer();
					//Validate(echo, answer);
					return true;
				} catch (Exception ex) {
					return false;
				}

			}
		}

		public override void Programme(IEnumerable<LED_Editor.Model.FrameStruct> frames) {
			IEnumerator<FrameStruct> it = frames.GetEnumerator();
			Query answer;
			State = ControllerState.Begin;


			try {
				OpenConnection();

				//if (!IsReadyForProgramming)
				//{
				//    ReportError("Контроллер не готов к программированию.");
				//}

				while (true) {
					switch (State) {
						case ControllerState.Begin:
							Query echo = Query.CreateInstance(QueryCodes.ECHO, Address);
							SendQuery(echo);


							State = ControllerState.Ready;

							break;
						case ControllerState.Ready:
							Query enterProgramming = Query.CreateInstance(QueryCodes.ENTER_PROG_MODE, Address);
							SendQuery(enterProgramming);
							//answer = (Query)WaitAnswer();
							//Validate(enterProgramming, answer);
							State = ControllerState.Programming;

							break;
						//break;
						case ControllerState.Programming:
							foreach (FrameStruct item in frames) {
								Query writeFrame = Query.CreateInstance(QueryCodes.WRITE_FRAME, Address, item.ToByteArray());
								SendQuery(writeFrame);
								//answer = (Query)WaitAnswer();
								//Validate(writeFrame, answer);
							}

							Query finalizeFrame = Query.CreateInstance(QueryCodes.FINILIZE_PROG, Address);
							SendQuery(finalizeFrame);
							//answer = (Query)WaitAnswer();
							//Validate(finalizeFrame, answer);

							State = ControllerState.End;
							break;
						case ControllerState.End:
							return;
						default:
							throw new NotSupportedException("Состояние контроллера " + State + " не поддерживается");
					}
				}
			} catch (ControllerException e) {
				State = ControllerState.End;
				Log("ОШИБКА: {0}. Подробнее: {1}", e.Message, e.InnerException == null ? "нет информации" : e.InnerException.Message);
				throw e;
			} finally {
				CloseConnection();
			}
		}


		protected void Validate(Query sendCommand, Query receiveCommand) {
			if (receiveCommand.Command != sendCommand.Command) {
				ReportError(string.Format("{0} Ожидался ответ {0}, получен {1}", sendCommand.Command, receiveCommand.Command));
			}
			if (receiveCommand.Type != QueryType.Confirm) {
				ReportError(string.Format("{0} Ожидалось подтверждение Confirm", sendCommand.Command));
			}
			if (receiveCommand.DataLength != 1) {
				ReportError(string.Format("{0} Ожидалось длина данных {1}, получено {2}", sendCommand.Command, 1, receiveCommand.DataLength));
			}
			//if ((ErrorCodes)receiveCommand.Data.First() != ErrorCodes.NO_ERROR) {
			//  ReportError(string.Format("{0} Ожидалось NO_ERROR: получено {1}", sendCommand.Command, ((ErrorCodes)receiveCommand.Data.First()).ToString()));
			//}
		}


		public override void SendQuery(object query) {
			if (query is IByteArray) {
				Log("-> Запрос: {0}", query.ToString());
				Debug.WriteLine("->    Send: " + query.ToString());
				SendQuery(((IByteArray)query).ToByteArray());
				Query answer = (Query)WaitAnswer();
				Validate((Query)query, answer);
			}
		}


		public override void SendQuery(byte[] query) {

		}


		public override object WaitAnswer() {
			Debug.WriteLine("-> Receive");
			return null;
		}



		#endregion
	}


	public interface IByteArray {
		byte[] ToByteArray();
	}
	public class Query : IByteArray {
		protected Query() {
		}

		private byte _queryType;
		private byte _address;
		private QueryCodes _command;
		private byte _dataLength;
		private byte[] _data;
		private byte _checkSum;

		public byte CheckSum {
			get {
				return _checkSum;
			}
			set {

				_checkSum = value;
			}
		}

		private bool _isCrcCalculated;


		public byte[] Data {
			get { return _data; }
			set { _data = value; }
		}

		public byte DataLength {
			get { return _dataLength; }
			set { _dataLength = value; }
		}


		public QueryCodes Command {
			get { return _command; }
			set { _command = value; }
		}


		public byte Type {
			get { return _queryType; }
			set { _queryType = value; }
		}

		public byte Address {
			get { return _address; }
			set { _address = value; }
		}

		public static readonly byte StartByte = 170;
		public static readonly byte EndByte = 85;


		#region IByteArray Members

		public byte[] ToByteArray() {
			List<byte> dataBytes = new List<byte>();

			dataBytes.Add(Query.StartByte);
			dataBytes.Add(Type);
			dataBytes.Add(Address);
			dataBytes.Add((byte)Command);
			dataBytes.Add(DataLength);
			if (Data != null) {
				dataBytes.AddRange(Data);
			}
			dataBytes.Add(CheckSum);
			dataBytes.Add(Query.EndByte);

			return dataBytes.ToArray();
		}

		#endregion

		public string Description {
			get {
				StringBuilder sb = new StringBuilder();
				sb.Append("Type: ");
				if (Type.Equals(QueryType.Request))
					sb.Append("Request");
				if (Type.Equals(QueryType.Confirm))
					sb.Append("Confirm");

				sb.Append("  Address: " + Address);
				sb.Append("  Command: " + Command);
				sb.Append("  DataLength: " + DataLength);

				return sb.ToString();
			}
		}

		protected void CalculateCRC() {
			int crc = Type + Address + (byte)Command + DataLength;
			if (Data != null) {
				foreach (byte item in Data) {
					crc += item;
				}
			}
			_checkSum = BitConverter.GetBytes(crc)[0];

			_isCrcCalculated = true;
		}

		public override string ToString() {
			return Description;
		}


		protected static QueryParser QueryParser {
			get {
				return QueryParser.Instance;
			}
		}


		public static Query CreateInstance(QueryCodes type, byte address, byte[] data) {
			Query query = new Query();
			query.Type = QueryType.Request;
			query.Address = address;
			query.Command = type;
			query.DataLength = Convert.ToByte(data.Length);
			query.Data = data;
			query.CalculateCRC();
			return query;
		}

		public static Query CreateInstance(QueryCodes type, byte address, byte data) {
			Query query = new Query();
			query.Type = QueryType.Request;
			query.Address = address;
			query.Command = type;
			query.DataLength = 1;
			query.Data = new byte[] { data };
			query.CalculateCRC();
			return query;
		}


		public static Query CreateInstance(QueryCodes type, byte address) {
			Query query = new Query();
			query.Type = QueryType.Request;
			query.Address = address;
			query.Command = type;
			query.DataLength = 0;
			query.CalculateCRC();
			return query;
		}

		public static Query CreateInstance(params object[] args) {
			return QueryParser.Parse(args.Cast<byte>().ToArray());
		}
	}


	public class QueryParser {
		private static QueryParser _QueryParser;

		public static QueryParser Instance {
			get {
				if (_QueryParser == null) {
					_QueryParser = new QueryParser();
				}
				return _QueryParser;
			}
		}



		public Query Parse(byte[] bytes) {
			Query query = Query.CreateInstance((QueryCodes)GetByteAt(bytes, 3), GetByteAt(bytes, 2));
			//Query query = new Query();
			if (Query.StartByte != GetByteAt(bytes, 0))
				throw new ControllerException("Start byte is invalid: " + GetByteAt(bytes, 0));


			query.Type = GetByteAt(bytes, 1);
			query.Address = GetByteAt(bytes, 2);
			query.Command = (QueryCodes)GetByteAt(bytes, 3);
			query.DataLength = GetByteAt(bytes, 4);


			List<byte> dataBytes = new List<byte>();
			for (int i = 0; i < query.DataLength; i++) {
				dataBytes.Add(GetByteAt(bytes, 5 + i));
			}
			query.Data = dataBytes.ToArray();
			query.CheckSum = GetByteAt(bytes, 5 + query.DataLength);

			if (Query.EndByte != GetByteAt(bytes, 5 + query.DataLength + 1))
				throw new ControllerException("End byte is invalid: " + GetByteAt(bytes, 5 + query.DataLength + 1));


			return query;
		}

		private byte GetByteAt(byte[] bytes, int index) {
			if (index < bytes.Length)
				return bytes.ElementAt(index);

			throw new InvalidOperationException("Byte at index " + index + " not found");

		}
	}


	public class QueryType {
		public static byte Request = Convert.ToByte('R');
		public static byte Confirm = Convert.ToByte('C');
	}

	//public class QueryCodes
	//{
	//    public static readonly byte ECHO = 0;	//Тест на наличие соединения
	//    public static readonly byte SET_COLOR = 1;	//Установить текущий цвет контроллера
	//    public static readonly byte RUN_PROGRAM = 2;	//Запустить программу, заложенную в контроллере
	//    public static readonly byte PAUSE_PROGRAM = 3;	//Приостановить выполнение программы
	//    public static readonly byte RESUME_PROGRAM = 4;	//Продолжить выполнение программы
	//    public static readonly byte CHANGE_SPEED = 5;	//Изменить скорость выполнения программы
	//    public static readonly byte CHANGE_BRIGHTNESS = 6;	//Изменить яркость
	//    public static readonly byte START_SYNC = 7;	//Начать синхронное выполнение программы
	//    public static readonly byte STOP_SYNC = 8;	//Остановить синхронное выполнение программы
	//    public static readonly byte ENTER_PROG_MODE = 9;	//Войти в режим программирования
	//    public static readonly byte WRITE_FRAME = 10;	//Записать кадр программы в память контроллера
	//    public static readonly byte FINILIZE_PROG = 11;	//Завершить запись программы в память контроллера
	//    /***************************************************************/
	//    public static readonly byte NO_ERROR = 0;	//Нет ошибки
	//    public static readonly byte CHECKSUM = 1;//	Ошибка контрольной суммы
	//    public static readonly byte UNKNOWN_COMMAND = 2;	//Неизвестный код команды
	//    public static readonly byte NON_EXPEXTED_COMMAND = 3;	//Неожидаемая команда
	//    public static readonly byte MEMORY_FULL = 4;	//Память заполнена


	//    //SET_COLOR	01	Установить текущий цвет контроллера
	//    //RUN_PROGRAM	02	Запустить программу, заложенную в контроллере
	//    //PAUSE_PROGRAM	03	Приостановить выполнение программы
	//    //RESUME_PROGRAM	04	Продолжить выполнение программы
	//    //CHANGE_SPEED	05	Изменить скорость выполнения программы
	//    //CHANGE_BRIGHTNESS	06	Изменить яркость
	//    //START_SYNC	07	Начать синхронное выполнение программы
	//    //STOP_SYNC	08	Остановить синхронное выполнение программы
	//    //ENTER_PROG_MODE	09	Войти в режим программирования
	//    //WRITE_FRAME	0A	Записать кадр программы в память контроллера
	//    //FINILIZE_PROG	0B	Завершить запись программы в память контроллера
	//}

	public enum ErrorCodes : byte {
		NO_ERROR = 0,	//Нет ошибки
		CHECKSUM = 1,//	Ошибка контрольной суммы
		UNKNOWN_COMMAND = 2,	//Неизвестный код команды
		NON_EXPEXTED_COMMAND = 3,	//Неожидаемая команда
		MEMORY_FULL = 4	//Память заполнена
	}

	public enum QueryCodes : byte {
		ECHO = 0,	//Тест на наличие соединения
		SET_COLOR = 1,	//Установить текущий цвет контроллера
		RUN_PROGRAM = 2,	//Запустить программу, заложенную в контроллере
		PAUSE_PROGRAM = 3,	//Приостановить выполнение программы
		RESUME_PROGRAM = 4,	//Продолжить выполнение программы
		CHANGE_SPEED = 5,	//Изменить скорость выполнения программы
		CHANGE_BRIGHTNESS = 6,	//Изменить яркость
		START_SYNC = 7,	//Начать синхронное выполнение программы
		STOP_SYNC = 8,	//Остановить синхронное выполнение программы
		ENTER_PROG_MODE = 9,	//Войти в режим программирования
		WRITE_FRAME = 10,	//Записать кадр программы в память контроллера
		FINILIZE_PROG = 11,	//Завершить запись программы в память контроллера



		//SET_COLOR	01	Установить текущий цвет контроллера
		//RUN_PROGRAM	02	Запустить программу, заложенную в контроллере
		//PAUSE_PROGRAM	03	Приостановить выполнение программы
		//RESUME_PROGRAM	04	Продолжить выполнение программы
		//CHANGE_SPEED	05	Изменить скорость выполнения программы
		//CHANGE_BRIGHTNESS	06	Изменить яркость
		//START_SYNC	07	Начать синхронное выполнение программы
		//STOP_SYNC	08	Остановить синхронное выполнение программы
		//ENTER_PROG_MODE	09	Войти в режим программирования
		//WRITE_FRAME	0A	Записать кадр программы в память контроллера
		//FINILIZE_PROG	0B	Завершить запись программы в память контроллера
	}

}
